}
let compiled = compiled.contains(dep.get_package_id());
- try!(compile(targets.as_slice(), dep, compiled, &mut cx, &mut queue));
+ try!(compile(targets.as_slice(), dep, pkg, compiled, &mut cx, &mut queue));
}
- try!(compile(targets, pkg, true, &mut cx, &mut queue));
+ try!(compile(targets, pkg, pkg, true, &mut cx, &mut queue));
// Now that we've figured out everything that we're going to do, do it!
try!(queue.execute(cx.config));
}
fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package,
- compiled: bool,
+ root_pkg: &'a Package, compiled: bool,
cx: &mut Context<'a, 'b>,
jobs: &mut JobQueue<'a, 'b>) -> CargoResult<()> {
debug!("compile_pkg; pkg={}; targets={}", pkg, targets);
}
jobs.enqueue(pkg, jq::StageStart, init);
- // Old custom build system
- // TODO: deprecated, remove
- let mut build_cmds = Vec::new();
- for (i, build_cmd) in pkg.get_manifest().get_build().iter().enumerate() {
- let work = try!(compile_custom_old(pkg, build_cmd.as_slice(), cx, i == 0));
- build_cmds.push(work);
- }
- let (freshness, dirty, fresh) =
- try!(fingerprint::prepare_build_cmd(cx, pkg));
- let desc = match build_cmds.len() {
- 0 => String::new(),
- 1 => pkg.get_manifest().get_build()[0].to_string(),
- _ => format!("custom build commands"),
- };
- let dirty = proc() {
- for cmd in build_cmds.into_iter() { try!(cmd()) }
- dirty()
- };
- jobs.enqueue(pkg, jq::StageCustomBuild, vec![(job(dirty, fresh, desc),
- freshness)]);
-
// After the custom command has run, execute rustc for all targets of our
// package.
//
vec![(rustdoc, KindTarget, desc)]
} else {
let req = cx.get_requirement(pkg, target);
- try!(rustc(pkg, target, cx, req))
+ let mut rustc = try!(rustc(pkg, target, cx, req));
+
+ if target.get_profile().is_custom_build() {
+ for &(ref mut work, _, _) in rustc.iter_mut() {
+ use std::mem;
+ let execute_cmd = try!(prepare_execute_custom_build(pkg, root_pkg,
+ target, cx));
+ let rustc_cmd = mem::replace(work, proc() Ok(()));
+ let replacement = proc() { try!(rustc_cmd()); execute_cmd() };
+ mem::replace(work, replacement);
+ }
+ }
+
+ rustc
};
let dst = match (target.is_lib(),
dst.push((job(dirty, fresh, desc), freshness));
}
}
- jobs.enqueue(pkg, jq::StageCustomBuild, builds);
+
+ if builds.len() >= 1 {
+ // New custom build system
+ jobs.enqueue(pkg, jq::StageCustomBuild, builds);
+
+ } else {
+ // Old custom build system
+ // TODO: deprecated, remove
+ let mut build_cmds = Vec::new();
+ for (i, build_cmd) in pkg.get_manifest().get_build().iter().enumerate() {
+ let work = try!(compile_custom_old(pkg, build_cmd.as_slice(), cx, i == 0));
+ build_cmds.push(work);
+ }
+ let (freshness, dirty, fresh) =
+ try!(fingerprint::prepare_build_cmd(cx, pkg));
+ let desc = match build_cmds.len() {
+ 0 => String::new(),
+ 1 => pkg.get_manifest().get_build()[0].to_string(),
+ _ => format!("custom build commands"),
+ };
+ let dirty = proc() {
+ for cmd in build_cmds.into_iter() { try!(cmd()) }
+ dirty()
+ };
+ jobs.enqueue(pkg, jq::StageCustomBuild, vec![(job(dirty, fresh, desc),
+ freshness)]);
+ }
+
jobs.enqueue(pkg, jq::StageLibraries, libs);
jobs.enqueue(pkg, jq::StageBinaries, bins);
jobs.enqueue(pkg, jq::StageTests, tests);
})
}
+// Prepares a `Work` that executes the target as a custom build script.
+// `pkg` is the package the build script belongs to, and `root_pkg` is the package
+// Cargo is being run on.
+fn prepare_execute_custom_build(pkg: &Package, root_pkg: &Package, target: &Target,
+ cx: &mut Context) -> CargoResult<Work> {
+ // TODO: this shouldn't explicitly pass `KindTarget` for dest/deps_dir, we
+ // may be building a C lib for a plugin
+ let layout = cx.layout(pkg, KindTarget);
+ let output = layout.native(pkg);
+ let old_output = layout.proxy().old_native(pkg);
+
+ // Building the command to execute
+ let to_exec = try!(cx.target_filenames(target));
+ if to_exec.len() >= 2 {
+ return Err(human(format!("custom build script shouldn't have multiple outputs")));
+ }
+ let to_exec = to_exec.into_iter().next();
+ let to_exec = match to_exec {
+ Some(cmd) => cmd,
+ None => return Err(human(format!("failed to determine output of custom build script"))),
+ };
+ let to_exec = layout.root().join(to_exec);
+
+ let profile = target.get_profile();
+ let mut p = process(to_exec, pkg, cx)
+ .env("OUT_DIR", Some(&output))
+ .env("CARGO_MANIFEST_DIR", Some(root_pkg.get_manifest_path()
+ .display().to_string()))
+ .env("NUM_JOBS", profile.get_codegen_units().map(|n| n.to_string()))
+ .env("TARGET", Some(cx.target_triple()))
+ .env("DEBUG", Some(profile.get_debug().to_string()))
+ .env("OPT_LEVEL", Some(profile.get_opt_level().to_string()))
+ .env("PROFILE", Some(profile.get_env()));
+
+ match cx.resolve.features(pkg.get_package_id()) {
+ Some(features) => {
+ for feat in features.iter() {
+ let feat = feat.as_slice().chars()
+ .map(|c| c.to_uppercase())
+ .map(|c| if c == '-' {'_'} else {c})
+ .collect::<String>();
+ p = p.env(format!("CARGO_FEATURE_{}", feat).as_slice(), Some("1"));
+ }
+ }
+ None => {}
+ }
+
+ let pkg = pkg.to_string();
+
+ Ok(proc() {
+ // TODO: is this necessary? it's already part of layout::prepare
+ try!(if old_output.exists() {
+ fs::rename(&old_output, &output)
+ } else {
+ fs::mkdir(&output, USER_RWX)
+ }.chain_error(|| {
+ internal("failed to create output directory for build command")
+ }));
+
+ try!(p.exec_with_output().map(|_| ()).map_err(|mut e| {
+ e.msg = format!("Failed to run custom build command for `{}`\n{}",
+ pkg, e.msg);
+ e.mark_human()
+ }));
+ Ok(())
+ })
+}
+
fn rustc(package: &Package, target: &Target,
cx: &mut Context, req: PlatformRequirement)
-> CargoResult<Vec<(Work, Kind, String)> >{
name = "foo"
version = "0.5.0"
authors = ["wycats@example.com"]
- build = 'build.rs'
+ build = "build.rs"
"#)
.file("src/main.rs", r#"
fn main() {}
assert_that(p.cargo_process("build"),
execs().with_status(101));
})
+
+test!(custom_build_script_failed {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [project]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ build = "build.rs"
+ "#)
+ .file("src/main.rs", r#"
+ fn main() {}
+ "#)
+ .file("build.rs", r#"
+ fn main() {
+ std::os::set_exit_status(101);
+ }
+ "#);
+ assert_that(p.cargo_process("build"),
+ execs().with_status(101)
+ .with_stderr(format!("\
+Failed to run custom build command for `foo v0.5.0 (file://{})`
+Process didn't exit successfully: `{}` (status=101)",
+p.root().display(), p.bin("build-script-build").display())));
+})
+
+test!(custom_build_env_vars {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [project]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [features]
+ bar_feat = ["bar/foo"]
+
+ [dependencies.bar]
+ path = "bar"
+ "#)
+ .file("src/main.rs", r#"
+ fn main() {}
+ "#)
+ .file("bar/Cargo.toml", r#"
+ [project]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ build = "build.rs"
+
+ [features]
+ foo = []
+ "#)
+ .file("bar/src/lib.rs", r#"
+ pub fn hello() {}
+ "#);
+
+ let file_content = format!(r#"
+ use std::os;
+ use std::io::fs::PathExtensions;
+ fn main() {{
+ let _target = os::getenv("TARGET").unwrap();
+
+ let _ncpus = os::getenv("NUM_JOBS").unwrap();
+
+ let out = os::getenv("CARGO_MANIFEST_DIR").unwrap();
+ let p1 = Path::new(out);
+ let p2 = os::make_absolute(&Path::new(file!()).dir_path().dir_path());
+ assert!(p1 == p2, "{{}} != {{}}", p1.display(), p2.display());
+
+ let opt = os::getenv("OPT_LEVEL").unwrap();
+ assert_eq!(opt.as_slice(), "0");
+
+ let opt = os::getenv("PROFILE").unwrap();
+ assert_eq!(opt.as_slice(), "compile");
+
+ let debug = os::getenv("DEBUG").unwrap();
+ assert_eq!(debug.as_slice(), "true");
+
+ let out = os::getenv("OUT_DIR").unwrap();
+ assert!(out.as_slice().starts_with(r"{0}"));
+ assert!(Path::new(out).is_dir());
+
+ let _feat = os::getenv("CARGO_FEATURE_FOO").unwrap();
+ }}
+ "#,
+ p.root().join("target").join("native").display());
+
+ let p = p.file("bar/build.rs", file_content);
+
+
+ assert_that(p.cargo_process("build").arg("--features").arg("bar_feat"),
+ execs().with_status(0));
+})